﻿// -----------------------------------------------------------------------
// <copyright file="DirectList{T}.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Utilities
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Similar to List&lt;T&gt; but with optional direct access to the internal T[].
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class DirectList<T> : IList<T>, ICollection<T>, IEnumerable<T>
    {
        /// <summary>
        /// 
        /// </summary>
        private T[] items;

        /// <summary>
        /// 
        /// </summary>
        private int count;

        /// <summary>
        /// 
        /// </summary>
        public DirectList()
        {
            this.Items = new T[0];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="capacity"></param>
        public DirectList(int capacity)
        {
            if (capacity < 0)
            {
                throw new ArgumentOutOfRangeException("capacity");
            }

            this.Items = new T[capacity];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="collection"></param>
        public DirectList(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            this.Items = new T[0];

            this.AddRange(collection);
        }

        /// <summary>
        /// 
        /// </summary>
        public T[] Items
        {
            get
            {
                return this.items;
            }

            protected set
            {
                this.items = value;

                if (this.count > this.items.Length)
                {
                    this.count = this.items.Length;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public int Capacity
        {
            get
            {
                return this.Items.Length;
            }

            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }

                if (this.Capacity == value)
                {
                    return;
                }

                var temp = new T[value];

                if (this.count > value)
                {
                    this.count = value;
                }

                Array.Copy(this.Items, 0, temp, 0, this.count);

                this.Items = temp;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public int Count
        {
            get
            {
                return this.count;
            }

            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("value");
                }

                if (value == this.count)
                {
                    return;
                }
                else if (value > this.count)
                {
                    this.EnsureCapacity(value);
                }
                else
                {
                    Array.Clear(this.Items, value, this.count - value);
                }

                this.count = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public T this[int index]
        {
            get
            {
                if (index < 0 || index >= this.Count)
                {
                    throw new ArgumentOutOfRangeException("index");
                }

                return this.Items[index];
            }

            set
            {
                if (index < 0 || index >= this.Count)
                {
                    throw new ArgumentOutOfRangeException("index");
                }

                this.Items[index] = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="collection"></param>
        public void AddRange(IEnumerable<T> collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            this.InsertRange(this.Count, collection);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="collection"></param>
        public void InsertRange(int index, IEnumerable<T> collection)
        {
            if (index < 0 || index > this.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }

            ICollection<T> coll = collection as ICollection<T>;

            if (coll == null)
            {
                coll = collection.ToList();
            }

            int count = coll.Count;

            if (count == 0)
            {
                return;
            }

            int count2 = this.Count;

            this.Count += count;

            if (index != count2)
            {
                Array.Copy(this.Items, index, this.Items, index + count, count);
            }

            coll.CopyTo(this.Items, index);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="count"></param>
        public void RemoveRange(int index, int count)
        {
            if (index < 0 || index >= this.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (count < 0 || index + count > this.Count)
            {
                throw new ArgumentOutOfRangeException("count");
            }

            if (count == 0)
            {
                return;
            }

            if (index + count != this.Count)
            {
                Array.Copy(this.Items, index + count, this.Items, index, this.Count - index - count);
            }

            this.Count -= count;
            Array.Clear(this.Items, this.Count, count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        public void CopyTo(T[] array)
        {
            this.CopyTo(0, array, 0, this.Count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        /// <param name="count"></param>
        public void CopyTo(int index, T[] array, int arrayIndex, int count)
        {
            if (index < 0 || index > this.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (array == null)
            {
                throw new ArgumentNullException("array");
            }

            if (arrayIndex < 0 || arrayIndex > array.Length)
            {
                throw new ArgumentOutOfRangeException("arrayIndex");
            }

            if (count < 0 || index + count > this.Count || arrayIndex + count > array.Length)
            {
                throw new ArgumentOutOfRangeException("count");
            }

            if (count == 0)
            {
                return;
            }

            Array.Copy(this.Items, index, array, arrayIndex, count);
        }

        #region IList<T> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public int IndexOf(T item)
        {
            return Array.IndexOf(this.Items, item, 0, this.Capacity);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        /// <param name="item"></param>
        public void Insert(int index, T item)
        {
            if (index < 0 || index > this.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            int count2 = this.Count;
            this.Count++;

            if (index != count2)
            {
                Array.Copy(this.Items, index, this.Items, index + 1, 1);
            }

            this.Items[index] = item;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="index"></param>
        public void RemoveAt(int index)
        {
            if (index < 0 || index >= this.Count)
            {
                throw new ArgumentOutOfRangeException("index");
            }

            if (index != this.Count - 1)
            {
                Array.Copy(this.Items, index + 1, this.Items, index, 1);
            }

            this.Count--;
            this.Items[this.Count] = default(T);
        }

        #endregion

        #region ICollection<T> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        public void Add(T item)
        {
            this.Insert(this.Count, item);
        }

        /// <summary>
        /// 
        /// </summary>
        public void Clear()
        {
            this.Count = 0;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Contains(T item)
        {
            return this.IndexOf(item) != -1;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="arrayIndex"></param>
        public void CopyTo(T[] array, int arrayIndex)
        {
            this.CopyTo(0, array, arrayIndex, this.Count);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(T item)
        {
            int ix = this.IndexOf(item);

            if (ix == -1)
            {
                return false;
            }

            this.RemoveAt(ix);
            return true;
        }

        #endregion

        #region IEnumerable<T> Members

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator<T> GetEnumerator()
        {
            return this.Items.Take(this.Count).GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="capacity"></param>
        protected void EnsureCapacity(int capacity)
        {
            if (capacity < 0)
            {
                throw new ArgumentOutOfRangeException("capacity");
            }

            if (this.Capacity >= capacity)
            {
                return;
            }

            int capacity2 = this.Capacity != 0 ? this.Capacity * 2 : 4;

            if (capacity2 < capacity)
            {
                capacity2 = capacity;
            }

            this.Capacity = capacity2;
        }
    }
}
